-- Copyright (c) 2024 Huawei Technologies Co., Ltd.
-- openUBMC is licensed under Mulan PSL v2.
-- You can use this software according to the terms and conditions of the Mulan PSL v2.
-- You may obtain a copy of Mulan PSL v2 at: http://license.coscl.org.cn/MulanPSL2
-- THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
-- EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
-- MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
-- See the Mulan PSL v2 for more details.

local device_object = require 'mc.mdb.device_tree.device_object'
local dev_object_manage = require 'mc.mdb.device_tree.dev_object_manage'
local utils = require 'device_tree.adapters.power_mgmt.utils'
local utils_core = require 'utils.core'
local skynet = require 'skynet'
local ctx = require 'mc.context'
local log = require 'mc.logging'
local org_freedesktop_dbus = require 'sd_bus.org_freedesktop_dbus'
local protocol = require 'device_tree.adapters.power_mgmt.protocol'
local match_rule = org_freedesktop_dbus.MatchRule
local debounce = require 'mc.debounce.debounce'
local enums = require 'macros.power_mgmt_enums'

local TIME_SERVICE_INTERVAL<const> = 1000 * 60 * 5
local REMOTE_POWER_MONITOR_SIG_PATH<const> = '/bmc/kepler/power_mgmt/psu'
local is_utf8 = utils_core.utf8_validate
local HEALTH_UPDATE_FAIL <const> = 0
local POWER_WATTS_UPDATE_FAIL <const> = 1
local PLUGIN_DEBOUNCE <const> = 2
-- 电源通讯异常初始值
local COMMUNICATION_STATUS_DEFAULT <const> = -1

local psu_slot = device_object('PsuSlot')

local FRU_SOURCE<const> = {
    NONE = 0,   -- 0 -- none
    EEPROM = 1, -- 1 -- 读取FRU的源为EEPROM
    PSU = 2     -- 2 -- 读取FRU的源为PSU
}

local PS_MONITOR<const> = {
    VOUT_STATUS   = 0, -- 输出电压状态
    IOUT_STATUS   = 1, -- 输出电流状态
    VIN_STATUS    = 2, -- 输入电压状态
    TEMPER_STATUS = 3, -- 电源温度传感器的状态
    FAN_STATUS    = 4, -- 电源风扇的状态
    CML_STATUS    = 5, -- 命令的状态
    MFR_STATUS    = 6, -- 厂商自定义的状态

    PS_MONITOR_END = 7
}

local PS_PRESENT = {
    PRESENT = 1,
    NOT_PRESENT = 0,
    DEFAULT_PRESENT = 255
}

local IS_NOT_HEALTH<const> = 0xff
local READ_FAIL_DEBOUNCE_COUNT <const> = 3
local E_OK = nil -- 函数执行成功返回nil
local E_FAILED = '' -- 空错误信息

local function get_pbit(val)
    return 1 << val
end

function psu_slot:ctor()
    self.protocol = false
    self.onepower_obj = false
    self.last_present = PS_PRESENT.DEFAULT_PRESENT
    self.prop_cb = {}
    self.prop_value = {}
    self.scan_status = 0
    self.ps_id = 0
    self.remote_power_monitor_task = false
    self.read_fail_count = 0
    self.remote_powe_signal = false
    self.last_read_interval = 10
    self.read_interval = 100
    self.monitor = false
    self.init_ok = false
    self.monitor_tasks = {}
    self.comm_status = {
        [HEALTH_UPDATE_FAIL] = COMMUNICATION_STATUS_DEFAULT,
        [POWER_WATTS_UPDATE_FAIL] = COMMUNICATION_STATUS_DEFAULT,
        [PLUGIN_DEBOUNCE] = debounce[debounce.DEBOUNCED_CONT_BIN].new(3, 1, 0)
    }
    self.board_type = "PEU"
end

function psu_slot:init()
    self.ps_id = self.SlotNumber
    self:declare_method('PowerSupply', 'bmc.dev.power_supply', 'GetReg', function (prop, ...)
        return self.protocol['get_' .. prop](self.protocol, ...)
    end)
    self:declare_method('PowerSupply', 'bmc.dev.power_supply', 'SetReg', function (prop, ...)
        return self.protocol['set_' .. prop](self.protocol, ...)
    end)
end

function psu_slot:is_present()
    return self.Presence == 1
end

function psu_slot:set_sleep_mode(sleep_mode)
    self.protocol:set_sleep_mode(sleep_mode)
    self:next_tick(function ()
        self:get_dynamic_data('sleep_mode')
    end)
end

function psu_slot:set_work_mode(mode)
    self.protocol:set_power_mode(mode)
    self:next_tick(function ()
        self:get_dynamic_data('work_mode')
    end)
end

function psu_slot:set_psu_fan_rpm(mode)
    self.protocol:set_psu_fan_speed_rpm(mode)
end

function psu_slot:get_psu_fan_rpm()
    return self.protocol:get_psu_fan_speed_rpm()
end

function psu_slot:get_psu_register_info(cmd, length)
    return self.protocol:get_psu_register_info(cmd, length)
end

function psu_slot:stop_monitor_tasks()
    local ts = self.monitor_tasks
    self.monitor_tasks = {}
    for _, task in pairs(ts) do
        task:stop()
    end
end

function psu_slot:psm_monitor_stop()
    log:notice('ps%d power monitor stop', self.ps_id)
    self:stop_monitor_tasks()
    self:stop_remote_power_monitor()
end

function psu_slot:power_upgrade(upgrade_path, upgrade_process, para_tab)
    self:psm_monitor_stop()
    self:dal_refresh_property('Health', IS_NOT_HEALTH)
    local ok, resp = pcall(function()
        return self.protocol:upgrade(upgrade_path, upgrade_process, para_tab)
    end)
    self:sleep_ms(self.read_interval * 10)
    self:get_power_supply_info('firmware_version')
    self:update_health_event()
    self:power_monitor_start()
    self:sleep_ms(self.read_interval * 20)
    if not ok then
        error(resp)
    end
    return resp
end

function psu_slot:get_fru_data(value)
    local fru_data = self.protocol:get_fru_data(value)
    self:dal_refresh_property('fru_data', fru_data)
    return fru_data
end

function psu_slot:dal_refresh_property(prop, value)
    self.prop_value[prop] = value
    if not self.prop_cb[prop] then
        return
    end
    for _, cb in pairs(self.prop_cb[prop]) do
        if self.init_ok then
            self:next_tick(cb, value)
        else
            -- init阶段需要把属性刷新完才允许分发到app侧
            cb(value)
        end
    end
end

function psu_slot:get_protocol(...)
    local protocol_name, req_protocol = protocol(...)
    if req_protocol then
        self.protocol = req_protocol.new(self, protocol_name)
        self:dal_refresh_property('protocol', protocol_name)
    end
end

local PS_TRY_CNT = 3 -- 重试次数
local PS_TRY_DELAY = 100 -- 重试时间间隔 ms
function psu_slot:get_dynamic_data(prop, retry_times)
    if not self.protocol or not self.protocol['get_' .. prop] then
        return
    end
    retry_times = retry_times or READ_FAIL_DEBOUNCE_COUNT
    local ok, resp
    for _ = 1, retry_times do
        ok, resp = pcall(self.protocol['get_' .. prop], self.protocol)
        if ok then
            self:dal_refresh_property(prop, resp)
            return E_OK, resp
        end
        self:sleep_ms(PS_TRY_DELAY)
    end
    return E_FAILED, resp
end

-- FP1600 电源模块不能直接通过pm bus 协议读取输出电压，需要通过输入电压除以4.5进行换算
function psu_slot:get_fp1600_output_voltage_data(prop, retry_times)
    if not self.protocol or not self.protocol['get_' .. prop] then
        return 
    end

    if not self.protocol['get_input_voltage'] then
        return
    end

    retry_times = retry_times or READ_FAIL_DEBOUNCE_COUNT
    local ok, resp
    for i = 1, retry_times do
        ok, resp = pcall(self.protocol['get_input_voltage'], self.protocol)
        if not ok then
            log:error('ps%s read data(input_voltage) failed. resp: %s', self.ps_id, resp)
            self:sleep_ms(PS_TRY_DELAY)
        end

        self:dal_refresh_property(prop, math.floor(resp / 4.5 + 0.5))
        return E_OK, resp
    end

    return E_FAILED, resp
end

function psu_slot:get_power_supply_info(prop)
    local ret, data = self:get_dynamic_data(prop, PS_TRY_CNT)
    if ret == E_OK then
        return data
    end
    log:error('ps%s read data(%s) failed. resp: %s', self.ps_id, prop, data)
end

function psu_slot:fetch_power_supply_info(physical_interface)
    local ok, resp = pcall(function ()
        self:get_protocol(physical_interface)
    end)
    if not ok then
        self.protocol = nil
        log:error('ps%s no protocol(%s). resp: %s', self.ps_id, physical_interface, resp)
        return
    end
    local manufacturer = self:get_power_supply_info('manufacturer')
    local model = self:get_power_supply_info('model')
    pcall(function ()
        self:get_protocol(physical_interface, manufacturer, model)
    end)
    self:get_power_supply_info('production_date')
    self:get_power_supply_info('product_version')
    self:get_power_supply_info('serial_number')
    self:get_power_supply_info('part_number')
    self:get_power_supply_info('firmware_version')
end

function psu_slot:clear_property_fetch_task()
    self.prop_cb = {}
    self.prop_value = {}
end

function psu_slot:fetch_property(prop, cb)
    if not self.prop_cb[prop] then
        self.prop_cb[prop] = {}
    end
    table.insert(self.prop_cb[prop], cb)
end

function psu_slot:remote_power_monitor()
    if self.remote_power_monitor_task and self.read_interval == self.last_read_interval then
        return true
    end
    local chip = self.PsuChip[enums.PSU_CHIP_OBJECT.BLOCKIO]
    local ok, resp = pcall(function ()
        local args = skynet.packstring(self.ps_id, REMOTE_POWER_MONITOR_SIG_PATH .. self.ps_id, self.read_interval)
        return skynet.unpack(chip:PluginRequest(ctx.new(), 'power_mgmt', "start_power_monitor", args))
    end)
    if not ok then
        local debounced_val = self.comm_status[PLUGIN_DEBOUNCE]:get_debounced_val(1)
        if debounced_val == 1 then
            self:process_communication_status(POWER_WATTS_UPDATE_FAIL, 1)
        end
        return
    end
    self.comm_status[PLUGIN_DEBOUNCE]:get_debounced_val(0)
    log:notice('start psu power monitor remote task, ps_id=%s, task_id=%s', self.ps_id, resp)
    self.last_read_interval = self.read_interval
    self.remote_power_monitor_task = resp
end

function psu_slot:start_to_receive_remote_power()
    local sig = match_rule.signal('UpdateProps'):with_path_namespace(REMOTE_POWER_MONITOR_SIG_PATH .. self.ps_id)
    self.remote_powe_signal = self.bus:match(sig, function(message)
        local _, need_update_vlaue = message:read('a{ss}a{sv}')
        for prop, val in pairs(need_update_vlaue) do
            if prop == 'communication_status' then
                self:process_communication_status(POWER_WATTS_UPDATE_FAIL, val)
            else
                log:debug("update %s to %s", prop, val)
                self:dal_refresh_property(prop, val)
                self.scan_status = self.scan_status | get_pbit(1)
                self:dal_refresh_property('scan_status', self.scan_status)
            end
        end
    end)
end

function psu_slot:stop_remote_power_monitor()
    log:notice('stop psu power monitor remote task, ps_id=%s, monitor task_id=%s',
        self.ps_id, self.remote_power_monitor_task)
    local args = skynet.packstring(self.remote_power_monitor_task)
    self.PsuChip[enums.PSU_CHIP_OBJECT.BLOCKIO]:PluginRequest(ctx.new(), 'power_mgmt', "stop_power_monitor", args)

    self.remote_power_monitor_task = false
end

function psu_slot:process_communication_status(err_type, flag)
    self.comm_status[err_type] = flag
    if err_type == POWER_WATTS_UPDATE_FAIL and flag == 1 then
        -- 电源通讯丢失，设置电源输入功耗扫描状态位为0
        self.scan_status = self.scan_status & ~get_pbit(1)
        self:dal_refresh_property('scan_status', self.scan_status)
        self:dal_refresh_property('input_power_watts', 0)
    end
    if self.comm_status[HEALTH_UPDATE_FAIL] == COMMUNICATION_STATUS_DEFAULT or
        self.comm_status[POWER_WATTS_UPDATE_FAIL] == COMMUNICATION_STATUS_DEFAULT then
        return
    end

    if self:is_present() and self.comm_status[HEALTH_UPDATE_FAIL] == 1 and
        self.comm_status[POWER_WATTS_UPDATE_FAIL] == 1 then
        log:debug('update communication_status to 1')
        self:dal_refresh_property('communication_status', 1)
    else
        log:debug('update communication_status to 0')
        self:dal_refresh_property('communication_status', 0)
    end
end

function psu_slot:update_health_event()
    local health_event, failed_flag = self.protocol:get_health_event()
    if not health_event then
        log:info('get health_event failed')
        return E_FAILED
    end

    if failed_flag then
        self:dal_refresh_property('health', get_pbit(4))
        if self.read_fail_count < READ_FAIL_DEBOUNCE_COUNT then
            self.read_fail_count = self.read_fail_count + 1
        else
            self:process_communication_status(HEALTH_UPDATE_FAIL, 1)
            self.scan_status = self.scan_status & ~get_pbit(0)
            self:dal_refresh_property('scan_status', self.scan_status)
        end
    else
        self.read_fail_count = 0
        self:process_communication_status(HEALTH_UPDATE_FAIL, 0)
        self:dal_refresh_property('health', health_event.event)
        self.scan_status = self.scan_status | get_pbit(0)
        self:dal_refresh_property('scan_status', self.scan_status)
    end

    self:dal_refresh_property('health_event', health_event)
    return E_OK
end

-- power_monitor 实际功率任务
function psu_slot:power_monitor()
    if self:update_health_event() ~= E_OK then
        -- 接口调用失败， 获取不到属性，直接返回
        return E_FAILED
    end
    if self.prop_value.health_event.event & get_pbit(4) == 0 then
        -- 开始监听 InputPowerWatts属性（频率100ms）
        self:remote_power_monitor()
    end
    return E_OK
end

function psu_slot:psm_task_power()
    local ret, _ = self:get_dynamic_data('input_power_watts')
    if ret == E_OK then
        self.scan_status = self.scan_status | get_pbit(1)
        self:dal_refresh_property('scan_status', self.scan_status)
    end
    while true do
        self:power_monitor()
        self:sleep_ms(500)
    end
end

function psu_slot:get_output_voltage()
    local model = self:get_power_supply_info('model')
    local rsp, _
    -- 获取电源型号判断是否为FP1600
    if model == enums.POWER_MODEL.FP1600 then
        rsp, _ = self:get_fp1600_output_voltage_data('output_voltage')
    else
        rsp, _ = self:get_dynamic_data('output_voltage')
    end
    return rsp, _
end

-- 查询电源输入/输出 电流/电压 等信息
function psu_slot:psm_task_v_i()
    local ret, rsp, _
    local read_fail_count = 0
    self:get_power_supply_info('rate')
    while true do
        rsp, _ = self:get_dynamic_data('output_power_watts')
        ret = ret or rsp
        rsp, _ = self:get_dynamic_data('input_current_amps')
        ret = ret or rsp
        rsp, _ = self:get_dynamic_data('output_current_amps')
        ret = ret or rsp

        rsp, _ = self:get_dynamic_data('input_voltage')
        ret = ret or rsp
        rsp, _ = self:get_output_voltage()
        ret = ret or rsp
        rsp, _ = self:get_dynamic_data('power_supply_type')
        ret = ret or rsp
        rsp, _ = self:get_dynamic_data('env_temperature_celsius')
        ret = ret or rsp
        rsp, _ = self:get_dynamic_data('primary_chip_temperature_celsius')
        ret = ret or rsp
        rsp, _ = self:get_dynamic_data('secondary_chip_temperature_celsius')
        ret = ret or rsp

        if ret ~= E_OK then
            if read_fail_count < READ_FAIL_DEBOUNCE_COUNT then
                read_fail_count = read_fail_count + 1
            else
                self.scan_status = self.scan_status & ~get_pbit(2)
            end
        else
            read_fail_count = 0
            self.scan_status = self.scan_status | get_pbit(2)
        end
        self:dal_refresh_property('scan_status', self.scan_status)
        ret = nil
        self:sleep_ms(3000)
    end
end

function psu_slot:psm_task_power_mode()
    local ret, rsp, _
    local read_fail_count = 0
    while true do
        rsp, _ = self:get_dynamic_data('work_mode')
        ret = ret or rsp
        rsp, _ = self:get_dynamic_data('sleep_mode')
        ret = ret or rsp
        rsp, _ = self:get_dynamic_data('normal_and_redundancy_supported')
        ret = ret or rsp
        if ret ~= E_OK then
            if read_fail_count < READ_FAIL_DEBOUNCE_COUNT then
                read_fail_count = read_fail_count + 1
            else
                self.scan_status = self.scan_status & ~get_pbit(3)
            end
        else
            read_fail_count = 0
            self.scan_status = self.scan_status | get_pbit(3)
        end
        self:dal_refresh_property('scan_status', self.scan_status)
        self:sleep_ms(2000)
    end
end

function psu_slot:time_service_task()
    while true do
        self.protocol:time_service()
        self:sleep_ms(TIME_SERVICE_INTERVAL)
    end
end

function psu_slot:power_monitor_entry()
    table.insert(self.monitor_tasks, self:next_tick(
        function()
            self:start_to_receive_remote_power()
            self:psm_task_power()
        end
    ))
    table.insert(self.monitor_tasks, self:next_tick(
        function()
            self:psm_task_v_i()
        end
    ))
    table.insert(self.monitor_tasks, self:next_tick(
        function()
            self:psm_task_power_mode()
        end
    ))
    table.insert(self.monitor_tasks, self:next_tick(
        function()
            self:time_service_task()
        end
    ))
end

function psu_slot:power_monitor_start()
    log:notice('ps%d power monitor start', self.ps_id)
    self:power_monitor_entry()
end

function psu_slot:get_black_box_data()
    return self.protocol:get_event_log()
end

function psu_slot:presence_detect()
    self:next_tick(function ()
        while true do
            if self.Presence == PS_PRESENT.DEFAULT_PRESENT then
                goto continue
            end
            if self.last_present == PS_PRESENT.DEFAULT_PRESENT then
                self.last_present = self.Presence
                goto continue
            end
            if self:is_present() and self.last_present == PS_PRESENT.NOT_PRESENT then
                utils.operation_log(nil, 'Plug in power supply %s by user', self.ps_id)
            elseif not self:is_present() and self.last_present == PS_PRESENT.PRESENT then
                utils.operation_log(nil, 'Plug out power supply %s by user', self.ps_id)
            end
            self.last_present = self.Presence
            ::continue::
            self:sleep_ms(1000)
        end
    end)
end

function psu_slot:start()
    self:presence_detect()
end

return psu_slot